//
// Created by jaesonzhang on 2024/9/11.
//

#include <iostream>
#include <string>
#include <locale>
#include <codecvt>
#include "JSApp.h"
#include "../CustomSkiaDraw.h"
#include "../canvas/CanvasJSBinding.h"
#include "../canvas/ContextJSBinding.h"


static JSValue js_render(JSContext *ctx, JSValueConst this_val,
                         int argc, JSValueConst *argv) {
  return JS_UNDEFINED;
}


// Helper function to convert jstring to std::string
static std::string jstringToString(JNIEnv *env, jstring jStr) {
  if (!jStr) {
    return "";
  }
  const char *chars = env->GetStringUTFChars(jStr, nullptr);
  std::string str(chars);
  env->ReleaseStringUTFChars(jStr, chars);
  return str;
}


JSApp::JSApp(jobject app) : jsApp(app) {
  jsEngine = std::make_shared<JSEngine>();
}

JSApp::~JSApp() {
  if (this->jSurface) {
    detachSurface(nullptr);
  }
  if (this->jsApp != nullptr) {
    extern JavaVM *g_vm;
    JNIEnv *env = nullptr;
    if (g_vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
      this->jsApp = nullptr;
      return;
    }
    env->DeleteGlobalRef(this->jsApp);
    this->jsApp = nullptr;
  }
}

void JSApp::start() {
  printf("onStart\n");

  // get js code
  extern JavaVM *g_vm;
  JNIEnv *env = nullptr;
  if (g_vm->GetEnv((void **) &env, JNI_VERSION_1_6) == JNI_OK) {
    extern jmethodID g_getJSAppCodeMethodID;
    auto jsAppCode =
        (jstring) env->CallObjectMethod(this->jsApp, g_getJSAppCodeMethodID);
    jsCode = jstringToString(env, jsAppCode);
  }

  jsEngine->create();
  auto that = this;
  std::packaged_task<void()> task([that] {
    // 设置关联对象
    auto jsContext = that->jsEngine->jsContext;
    auto jsRuntime = that->jsEngine->jsRuntime;
    JS_SetContextOpaque(jsContext, that);

    JSValue global_obj;
    global_obj = JS_GetGlobalObject(jsContext);
    JS_SetPropertyStr(jsContext, global_obj, "render",
                      JS_NewCFunction(jsContext,
                                      js_render,
                                      "render",
                                      1));


    js_canvas_init(jsContext);
    js_context_init(jsContext);
  });
  jsEngine->enqueueTask(task);
  auto jsAppCore = "class App {\n"
                   "    constructor() {\n"
                   "       \n"
                   "    }\n"
                   "\n"
                   "    onCreate() {\n"
                   "    }\n"
                   "\n"
                   "    onResume() {\n"
                   "    }\n"
                   "\n"
                   "    onPause() {\n"
                   "    }\n"
                   "\n"
                   "    onStop() {\n"
                   "    }\n"
                   "\n"
                   "    onDestroy() {\n"
                   "    }\n"
                   "}\n"
                   "globalThis.gApp = {};\n"
                   "console.log('aha, create gApp');\n"
                   "function registerApp(app) {\n"
                   "    console.log('set app');\n"
                   "    globalThis.gApp = app;\n"
                   "}";
  jsEngine->run(jsAppCore, strlen(jsAppCore), "app_core.js");
  // 加载js环境
  // 解压app包
  // 加载app.js
  // 执行js(这之前的步骤，可以放在单独的一个预加载中，以加快渲染。
  // 不过具体要看一下执行时间。 执行js时，需要一个 jSurface 以完成页面绘制。)
}

void JSApp::stop() {
  callJSMethod("if(gApp.onStop){gApp.onStop();}");
}

void JSApp::halt() {
  callJSMethod("if(gApp.onPause){gApp.onPause();}");
}

void JSApp::resume() {
  callJSMethod("if(gApp.onResume){gApp.onResume();}");
}

void JSApp::destroy() {
  callJSMethod("if(gApp.onDestroy){gApp.onDestroy();}");
  if (this->jsAppValue) {
    JS_FreeValue(jsEngine->jsContext, *jsAppValue);
  }
  jsEngine->destroy();
  detachSurface(nullptr);
}

void JSApp::attachSurface(jobject surface, int w, int h) {
  printf("attachSurface, need redraw\n");
  extern JavaVM *g_vm;
  JNIEnv *env = nullptr;
  if (g_vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
    return;
  }
  this->jSurface = env->NewGlobalRef(surface);
  bool sizeChanged = this->width != w || this->height != h;
  this->width = w;
  this->height = h;

  forceRender = sizeChanged;
  if (forceRender) {
    forceRender = false;
    doCreateJSApp(w, h);
  } else {
    render(false);
  }
}

void JSApp::doCreateJSApp(int w, int h) {
  SkImageInfo
      info = SkImageInfo::MakeS32(w, h, SkAlphaType::kPremul_SkAlphaType);
  // 分配目标Bitmap的像素内存
  this->cacheBitmap.allocPixels(info);
  auto len = strlen(jsCode.c_str());
  if (len > 0) {
    this->jsEngine->run(jsCode.c_str(), len, "app.js");
    this->callJSMethod("if(gApp.onCreate){gApp.onCreate();}");
  } else {
    printf("error: please provide a non-empty js app code.\n");
  }
}


void JSApp::detachSurface(jobject surface) {
  printf("detachSurface\n");
  if (this->jSurface != nullptr) {
    extern JavaVM *g_vm;
    JNIEnv *env = nullptr;
    if (g_vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
      this->jSurface = nullptr;
      return;
    }
    env->DeleteGlobalRef(this->jSurface);
    this->jSurface = nullptr;
  }
}

void JSApp::render(bool shouldAttach) {
  if (this->jSurface == nullptr) {
    printf("surface null\n");
    return;
  }
  JNIEnv *env = nullptr;
  extern JavaVM *g_vm;
  // 将当前线程附加到 JVM
  if (g_vm->AttachCurrentThread(&env, nullptr) != JNI_OK) {
    printf("Failed to attach current thread to JVM");
    return;
  }

  ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, this->jSurface);
  ANativeWindow_setBuffersGeometry(nativeWindow,
                                   width,
                                   height,
                                   WINDOW_FORMAT_RGBA_8888);

  ANativeWindow_Buffer *buffer = new ANativeWindow_Buffer();
  ANativeWindow_lock(nativeWindow, buffer, 0);

  int bpr = buffer->stride * 4;
  SkImageInfo image_info = SkImageInfo::MakeS32(buffer->width,
                                                buffer->height,
                                                SkAlphaType::kPremul_SkAlphaType);
  SkBitmap bitmap;
  bitmap.setInfo(image_info, bpr);
  bitmap.setPixels(buffer->bits);

  // 获取源Bitmap的像素指针
  const void *srcPixels = cacheBitmap.getPixels();
  // 获取目标Bitmap的像素指针
  void *dstPixels = bitmap.getPixels();
  // 计算出字节数
  size_t srcRowBytes = cacheBitmap.rowBytes();
  size_t dstRowBytes = bitmap.rowBytes();

  // 复制像素数据
  for (int y = 0; y < height; ++y) {
    memcpy((char *) dstPixels + y * dstRowBytes,
           (const char *) srcPixels + y * srcRowBytes,
           srcRowBytes);
  }

  // 提交
  ANativeWindow_unlockAndPost(nativeWindow);

  if (shouldAttach) {
    // 完成操作后，分离当前线程
    g_vm->DetachCurrentThread();
  }
  free(buffer);
}

void JSApp::onRender() {
  printf("onRender\n");
  render();
}

void JSApp::requestRender(SkBitmap bitmap, int w, int h) {
  // 获取源Bitmap的像素指针
  const void *srcPixels = bitmap.getPixels();
  // 获取目标Bitmap的像素指针
  void *dstPixels = cacheBitmap.getPixels();
  // 计算出字节数
  size_t srcRowBytes = bitmap.rowBytes();
  size_t dstRowBytes = cacheBitmap.rowBytes();

  // 复制像素数据
  for (int y = 0; y < h; ++y) {
    memcpy((char *) dstPixels + y * dstRowBytes,
           (const char *) srcPixels + y * srcRowBytes,
           srcRowBytes);
  }
  render();
}

void JSApp::callJSMethod(const char *methodName) {
  jsEngine->run(methodName,
                strlen(methodName),
                "app_call_js_method.js");
}